using System;
using System.Collections.Generic;
using System.Linq;
using Engine;
using Engine.OldSerialization;

namespace RuthlessConquest {
    public class WiseAIPlayer : Player {
        public class AttackPlan : ISerializable {
            public Planet Target;

            public int ReinforcementsCount;

            public int ShipsAvailable;

            public int Distance;

            public long ScoreStrength;

            public long ScoreStrategy;

            public DynamicArray<AttackRoute> AttackRoutes = new();

            public void Serialize(InputArchive archive) {
                archive.Serialize("Target", ref Target);
                archive.Serialize("ReinforcementsCount", ref ReinforcementsCount);
                archive.Serialize("ShipsAvailable", ref ShipsAvailable);
                archive.Serialize("Distance", ref Distance);
                archive.Serialize("ScoreStrength", ref ScoreStrength);
                archive.Serialize("ScoreStrategy", ref ScoreStrategy);
                archive.Serialize("AttackRoutes", ref AttackRoutes);
            }

            public void Serialize(OutputArchive archive) {
                archive.Serialize("Target", Target);
                archive.Serialize("ReinforcementsCount", ReinforcementsCount);
                archive.Serialize("ShipsAvailable", ShipsAvailable);
                archive.Serialize("Distance", Distance);
                archive.Serialize("ScoreStrength", ScoreStrength);
                archive.Serialize("ScoreStrategy", ScoreStrategy);
                archive.Serialize("AttackRoutes", AttackRoutes);
            }
        }

        public class AttackRoute : ISerializable {
            public Planet Source;

            public int Ships;

            public void Serialize(InputArchive archive) {
                archive.Serialize("Source", ref Source);
                archive.Serialize("Ships", ref Ships);
            }

            public void Serialize(OutputArchive archive) {
                archive.Serialize("Source", Source);
                archive.Serialize("Ships", Ships);
            }
        }

        public int Level;

        public int NextStep;

        public AttackPlan BestStrategyPlan;

        public WiseAIPlayer() { }

        public WiseAIPlayer(PlayerDescription playerDescription, int level) : base(playerDescription) => Level = level;

        public override void Serialize(InputArchive archive) {
            base.Serialize(archive);
            archive.Serialize("Level", ref Level);
            archive.Serialize("NextStep", ref NextStep);
            archive.Serialize("BestStrategyPlan", ref BestStrategyPlan);
        }

        public override void Serialize(OutputArchive archive) {
            base.Serialize(archive);
            archive.Serialize("Level", Level);
            archive.Serialize("NextStep", NextStep);
            archive.Serialize("BestStrategyPlan", BestStrategyPlan);
        }

        public override void Draw(Color colorTransform) { }

        public override void Step() {
            base.Step();
            int num;
            int num2;
            switch (Level) {
                case 0:
                    num = 180;
                    num2 = 1;
                    break;
                case 1:
                    num = 120;
                    num2 = 2;
                    break;
                case 2:
                    num = 60;
                    num2 = 3;
                    break;
                default: throw new InvalidOperationException("Invalid level");
            }
            if (BestStrategyPlan != null
                && BestStrategyPlan.Target.Faction == Faction) {
                BestStrategyPlan = null;
            }
            if (StepModule.StepIndex >= NextStep) {
                NextStep = StepModule.StepIndex + StepModule.Random.Int(num / 2, num);
                for (int i = 0; i < num2; i++) {
                    Move();
                }
                Attack();
                Satellite();
            }
        }

        public void Move() {
            if (Game.CreationParameters.ShipRange > 20000) {
                return;
            }
            List<Planet> list = PlanetsModule.Planets.Where(p => p.Faction == Faction).ToList();
            if (list.Count <= 0) {
                return;
            }
            List<Planet> list2 = (from p in PlanetsModule.Planets.Where(p => p.Faction != Faction).ToList()
                where p.Faction != Faction && p.Faction != Faction.Neutral
                select p).ToList();
            int num = 0;
            Planet planet = null;
            int num2 = 0;
            DynamicArray<Planet> dynamicArray = null;
            for (int i = 0; i < 100; i++) {
                Planet source = list[StepModule.Random.Int(list.Count)];
                Planet target = list[StepModule.Random.Int(list.Count)];
                if (source == target
                    || source.ShipsCount < 8) {
                    continue;
                }
                int routeDistance = RouteFinderModule.GetRouteDistance(source, target);
                if (routeDistance <= 0) {
                    continue;
                }
                int num3 = list2.Where(p => ShipsModule.TestReachability(p.Position, source.Position)).Sum(p => p.ShipsCount);
                int num4 = source.ShipsCount - num3 * 7 / 10;
                if (num4 < 8) {
                    continue;
                }
                int num5 = SafeMin(20000, list2, p => RouteFinderModule.GetRouteDistance(source, p));
                int num6 = SafeMin(20000, list2, p => RouteFinderModule.GetRouteDistance(target, p));
                int num7 = 100 * (num5 - num6) / routeDistance;
                if (BestStrategyPlan != null) {
                    if (BestStrategyPlan.AttackRoutes.Any(r => r.Source == target)) {
                        num7 += 60;
                    }
                    if (BestStrategyPlan.AttackRoutes.Any(r => r.Source == source)) {
                        num7 -= 1000;
                    }
                }
                if (source.ShipsCount >= target.MaxShipsCount * 9 / 10) {
                    num7 += 25;
                }
                if (target.ShipsCount >= target.MaxShipsCount * 9 / 10) {
                    num7 -= target.CanLaunchSatellite(true) ? 25 : 50;
                }
                if (source.SizeClass == 0
                    && target.SizeClass != 0) {
                    num7 += 5;
                }
                if (num7 > num) {
                    DynamicArray<Planet> dynamicArray2 = RouteFinderModule.FindRoute(source, target, false);
                    if (dynamicArray2.Count > 0) {
                        num = num7;
                        planet = source;
                        dynamicArray = dynamicArray2;
                        num2 = num4;
                    }
                }
            }
            if (num > 10
                && planet != null
                && dynamicArray != null
                && num2 > 0) {
                ShipsModule.SendShips(planet, dynamicArray, num2, Faction.None);
            }
        }

        public void Attack() {
            int num;
            int num2;
            switch (Level) {
                case 0:
                    num = 15;
                    num2 = 100;
                    break;
                case 1:
                    num = 5;
                    num2 = 80;
                    break;
                case 2:
                    num = 3;
                    num2 = 60;
                    break;
                default: throw new InvalidOperationException("Invalid level");
            }
            List<Planet> list = PlanetsModule.Planets.Where(p => p.Faction == Faction).ToList();
            List<Planet> list2 = PlanetsModule.Planets.Where(p => p.Faction != Faction).ToList();
            List<Planet> source = list2.Where(p => p.Faction != Faction && p.Faction != Faction.Neutral).ToList();
            Faction faction = FindStrongestEnemyFaction();
            if (list.Count <= 0
                || list2.Count <= 0) {
                return;
            }
            DynamicArray<AttackPlan> dynamicArray = new();
            foreach (Planet ep in list2) {
                if (ShipsModule.Ships.Any(s => s.Faction == Faction && s.Route.Contains(ep))) {
                    continue;
                }
                int num3 = SafeMin(20000, list, p => IntMath.ApproxDistance(p.Position, ep.Position)) + 3000;
                AttackPlan plan = new() { Target = ep };
                foreach (Planet item in list) {
                    if (!ShipsModule.TestReachability(item.Position, ep.Position)) {
                        continue;
                    }
                    int num4 = IntMath.ApproxDistance(ep.Position, item.Position);
                    if (num4 <= num3) {
                        int num5 = SafeMin(20000, source.Where(p => p != ep), p => RouteFinderModule.GetRouteDistance(plan.Target, p));
                        int num6 = MathUtils.Clamp(100 * num5 / 5000, 50, 100);
                        int num7 = item.ShipsCount * num6 / 100;
                        plan.ShipsAvailable += num7;
                        plan.AttackRoutes.Add(new AttackRoute { Source = item, Ships = num7 });
                        plan.Distance = MathUtils.Max(plan.Distance, num4);
                    }
                }
                if (plan.AttackRoutes.Count > 0) {
                    plan.ReinforcementsCount = list2.Where(p => p != ep && ShipsModule.TestReachability(p.Position, ep.Position))
                        .Sum(p => p.ShipsCount);
                    dynamicArray.Add(plan);
                }
            }
            foreach (AttackPlan plan2 in dynamicArray) {
                int num8 = SafeMin(
                    20000,
                    source.Where(p => p.Faction != plan2.Target.Faction),
                    p => RouteFinderModule.GetRouteDistance(plan2.Target, p)
                );
                int num9 = plan2.Target.ShipsCount + plan2.ReinforcementsCount / 5;
                for (int i = 0; i < plan2.Target.Satellites.Count(); i++) {
                    num9 = num9 * 3 / 2 + 15;
                }
                plan2.ScoreStrength += MathUtils.Min(100 * plan2.ShipsAvailable / (num9 + 1), 1000);
                plan2.ScoreStrategy = 1L;
                plan2.ScoreStrategy += 10000 / (plan2.Target.ShipsCount + plan2.ReinforcementsCount / 5 + 1);
                plan2.ScoreStrategy *= 10000L;
                plan2.ScoreStrategy /= plan2.Target.ProductionPeriod + 50;
                plan2.ScoreStrategy *= 1000L;
                plan2.ScoreStrategy /= 1000 + plan2.Distance;
                if (plan2.Target.Faction == Faction.Neutral) {
                    plan2.ScoreStrategy = plan2.ScoreStrategy * 80 / 100;
                }
                if (plan2.Target.IsSpecial) {
                    plan2.ScoreStrategy *= 2L;
                }
                plan2.ScoreStrategy *= 2 + plan2.AttackRoutes.Count;
                for (int j = 0; j < plan2.Target.Satellites.Count(); j++) {
                    plan2.ScoreStrategy = plan2.ScoreStrategy * 9 / 10;
                }
                if (Level > 0) {
                    plan2.ScoreStrategy *= 1000 + num8;
                    if (plan2.Target.Faction == faction) {
                        plan2.ScoreStrategy = plan2.ScoreStrategy * 15 / 10;
                    }
                }
            }
            BestStrategyPlan = dynamicArray.OrderByDescending(p => p.ScoreStrategy).FirstOrDefault();
            AttackPlan attackPlan = dynamicArray.OrderByDescending(p => p.ScoreStrength * p.ScoreStrategy).FirstOrDefault();
            if (attackPlan == null) {
                return;
            }
            bool flag = attackPlan.Target.Faction == Faction.Neutral;
            int num10 = flag ? 5 : 25;
            int num11 = 100 * attackPlan.ShipsAvailable / (attackPlan.Target.ShipsCount + num10);
            if (attackPlan.Target.DefenceFactor > 0) {
                num11 = num11 * attackPlan.Target.DefenceFactor / 100;
            }
            for (int k = 0; k < attackPlan.Target.Satellites.Count(); k++) {
                num11 = num11 * 2 / 3;
            }
            if (num11 <= 0) {
                return;
            }
            int num12 = flag ? 100 : 120;
            int num13 = num11 < 80 ? 2 * num :
                num11 < num12 ? 6 * num :
                num11 >= 200 ? 500 : 250;
            if (StepModule.Random.Int(1000) >= num13) {
                return;
            }
            foreach (AttackRoute attackRoute in attackPlan.AttackRoutes) {
                DynamicArray<Planet> dynamicArray2 = RouteFinderModule.FindRoute(attackRoute.Source, attackPlan.Target, true);
                if (dynamicArray2.Count > 0) {
                    int num14 = MathUtils.Min(attackRoute.Ships * 100 / num11, attackRoute.Ships);
                    int num15 = (attackRoute.Ships - num14) * num2 / 100;
                    int count = MathUtils.Clamp(num14 + num15, 0, attackRoute.Ships);
                    ShipsModule.SendShips(attackRoute.Source, dynamicArray2, count, Faction.None);
                }
            }
            if (BestStrategyPlan == attackPlan) {
                BestStrategyPlan = null;
            }
        }

        public void Satellite() {
            Planet planet = null;
            int num = 0;
            List<Planet> list = PlanetsModule.Planets.Where(p => p.Faction == Faction).ToList();
            Faction faction = FindStrongestEnemyFaction();
            foreach (Planet planet2 in list) {
                if (planet2.CanLaunchSatellite(false)
                    && planet2.ShipsCount > planet2.GetSatelliteCost() + 70) {
                    int x = PlanetsModule.Planets.Where(p => p.Faction != Faction && p.Faction != Faction.Neutral)
                        .ToList()
                        .Count(p => ShipsModule.TestReachability(planet2.Position, p.Position));
                    bool num2 = IntMath.ApproxLength(100 * (planet2.Position - Game.WorldSize / 2) / (Game.WorldSize / 2)) < 60;
                    int num3 = 10 * MathUtils.Min(x, 4);
                    if (num2) {
                        num3 *= 2;
                    }
                    if (planet2.Faction == faction) {
                        num3 *= 2;
                    }
                    if (num3 > num) {
                        planet = planet2;
                        num = num3;
                    }
                }
            }
            planet?.LaunchSatellite();
        }

        public Faction FindStrongestEnemyFaction() {
            int num = -1;
            Faction result = Faction.Neutral;
            Faction faction = Faction.Faction1;
            while (faction <= Faction.Faction6) {
                if (faction != Faction) {
                    int num2 = 0;
                    foreach (Planet item in PlanetsModule.Planets.Where(p => p.Faction == faction)) {
                        if (item.ProductionPeriod > 0) {
                            num2 += 10000 / item.ProductionPeriod;
                        }
                    }
                    if (num2 > num) {
                        num = num2;
                        result = faction;
                    }
                }
                Faction faction2 = faction + 1;
                faction = faction2;
            }
            return result;
        }

        public static int SafeMin(int initial, IEnumerable<Planet> e, Func<Planet, int> selector) {
            int num = initial;
            foreach (Planet item in e) {
                num = Math.Min(selector(item), num);
            }
            return num;
        }
    }
}